#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/power_supply.h>

#define PD_AND_CHARGER_NOTIFIY 1

#if PD_AND_CHARGER_NOTIFIY
#include <inc/tcpci_core.h>
#include <linux/notifier.h>
#endif

/* Define the GENMASK macro */
#define CHR_GENMASK(value, high, low, shift)  ((value & GENMASK(high, low)) >> shift)

/* CHR_REG07 bit7: IINDET_EN */
#define CHR_IINDET_EN   BIT(7)

/* CHR_REG0A bit7: VBUS_GD */
#define CHR_VBUS_GD     BIT(7)

/* Common enum type */
enum chr_charge_mode {
    CHR_IPRECHG = 0,
    CHR_ICHG = 1,
    CHR_ITERM = 2,
};

/*
* Charger charge mode
* @OTG_MODE: OTG mode
* @CHARGE_MODE: Charge mode
* @ERROR_MODE: Error mode
*/
enum chr_mode {
    OTG_MODE = 1,
    CHARGE_MODE = 2,
    ERROR_MODE = 3,
};

enum chr_device_id {
    CHR_DEVICE_ID_ET95601CH = 0,
};

enum charger_reg_map {
    CHR_REG00 = 0x00,
    CHR_REG01 = 0x01,
    CHR_REG02 = 0x02,
    CHR_REG03 = 0x03,
    CHR_REG04 = 0x04,
    CHR_REG05 = 0x05,
    CHR_REG06 = 0x06,
    CHR_REG07 = 0x07,
    CHR_REG08 = 0x08,
    CHR_REG09 = 0x09,
    CHR_REG0A = 0x0A,
    CHR_REG0B = 0x0B,
    CHR_REG0C = 0x0C,
    CHR_REG0D = 0x0D,
    CHR_REG0E = 0x0E,
    CHR_REG0F = 0x0F,
    CHR_REG10 = 0x10,
    CHR_REG11 = 0x11,
};

/* Common struct type */
struct chr_ops;

/* 
* Charger data structure
* @input_v: Input voltage
* @input_i: Input current
* @iprechg_i: Precharge current
* @iterm_i: Termcharge current
* @fast_charge_i: Fastcharge current
* @fast_charge_v: Fastcharge voltage
* @boost_v: Boost voltage
* @boost_i: Boost current
*/
struct chr_charger_data {
    unsigned int input_v;
    unsigned int input_i;
    unsigned int iprechg_i;
    unsigned int iterm_i;
    unsigned int fast_charge_i;
    unsigned int fast_charge_v;
    unsigned int boost_v;
    unsigned int boost_i;
};

/*
* Charger data structure
* @name: Charger name
* @id: Charger id
* @regmap_config: Charger register map config
* @ops: Charger operations
*/
struct etek_chr_info {
    const char *name;
    unsigned int id;
    struct regmap_config *regmap_config;
    struct chr_ops *ops;
    struct power_supply_desc *psy_desc;
};

/*
* Charger state structure
* @prev_vbus_state: Previous vbus state
* @curr_vbus_state: Current vbus state
*/
struct chr_state {
    unsigned int prev_vbus_state;
    unsigned int curr_vbus_state;
};

/*
* Charger data structure
* @client: Charger i2c client
* @dev: Charger device
* @regmap: Charger register map
* @mutex: Charger mutex
* @chr_info: Charger information
* @charger_data: Charger data
* @gpio_num: Charger gpio number
* @irq_num: Charger irq number
*/
struct chr_data {
    struct i2c_client *client;
    struct device *dev;
    struct regmap *regmap;
    struct etek_chr_info *chr_info;
#if PD_AND_CHARGER_NOTIFIY
    struct tcpc_device *tcpc;
    struct notifier_block nb;
#endif
    struct mutex mutex;
    struct delayed_work work;
    struct power_supply *psy;
    struct chr_state state;
    struct chr_charger_data charger_data;
    unsigned int gpio_num;
    unsigned int irq_num;
};

/*
* Charger operations
* @set_input_current: Set input current
* @set_input_voltage: Set input voltage
* @set_iprecharge_current: Set precharge current
* @set_itermcharge_current: Set termcharge current
* @set_fastcharge_current: Set fastcharge current
* @set_charge_voltage: Set charge voltage
* @set_otg_boost_current: Set otg boost current
* @set_otg_boost_voltage: Set otg boost voltage
* @set_charge_mode: Set charge mode
* @get_input_current: Get input current
* @get_input_voltage: Get input voltage
* @get_iprecharge_current: Get precharge current
* @get_itermcharge_current: Get termcharge current
* @get_fastcharge_current: Get fastcharge current
* @get_charge_voltage: Get charge voltage
* @get_otg_boost_current: Get otg boost current
* @get_otg_boost_voltage: Get otg boost voltage
* @get_charge_mode: Get charge mode
*/
struct chr_ops {
    int (*set_input_current)(struct chr_data *chr_data, unsigned int cur);
    int (*set_input_voltage)(struct chr_data *chr_data, unsigned int vol);
    int (*set_iprecharge_current)(struct chr_data *chr_data, unsigned int cur);
    int (*set_itermcharge_current)(struct chr_data *chr_data, unsigned int cur);
    int (*set_fastcharge_current)(struct chr_data *chr_data, unsigned int cur);
    int (*set_charge_voltage)(struct chr_data *chr_data, unsigned int vol);
    int (*set_otg_boost_current)(struct chr_data *chr_data, unsigned int cur);
    int (*set_otg_boost_voltage)(struct chr_data *chr_data, unsigned int vol);
    int (*set_charge_mode)(struct chr_data *chr_data, enum chr_mode mode);
    int (*get_input_current)(struct chr_data *chr_data);
    int (*get_input_voltage)(struct chr_data *chr_data);
    int (*get_iprecharge_current)(struct chr_data *chr_data);
    int (*get_itermcharge_current)(struct chr_data *chr_data);
    int (*get_fastcharge_current)(struct chr_data *chr_data);
    int (*get_charge_voltage)(struct chr_data *chr_data);
    int (*get_otg_boost_current)(struct chr_data *chr_data);
    int (*get_otg_boost_voltage)(struct chr_data *chr_data);
    enum chr_mode (*get_charge_mode)(struct chr_data *chr_data);
    int (*get_charge_type)(struct chr_data *chr_data);
    int (*get_charge_status)(struct chr_data *chr_data);
};

#if PD_AND_CHARGER_NOTIFIY
extern struct class *tcpc_class;

static int charger_match_device_by_name(struct device *dev, const void *data)
{
	const char *name = data;
	struct tcpc_device *tcpc = dev_get_drvdata(dev);

	return strcmp(tcpc->desc.name, name) == 0;
}

struct tcpc_device *charger_dev_get_by_name(const char *name)
{
	struct device *dev = class_find_device(tcpc_class,
			NULL, (const void *)name, charger_match_device_by_name);
	return dev ? dev_get_drvdata(dev) : NULL;
}

static int charger_srcu_notifier_func(struct notifier_block *nb, unsigned long action, void *data)
{
    struct chr_data *chr_data = container_of(nb, struct chr_data, nb);
    struct tcp_notify *tcp_noti = data;

    dev_info(chr_data->dev, "%s start\n", __func__);
    if (!tcp_noti)
        return -EINVAL;

    mutex_lock(&chr_data->mutex);
    switch (action) {
        case TCP_NOTIFY_SOURCE_VBUS:
            dev_info(chr_data->dev, "source vbus mv:%d ma:%d\n",
                    tcp_noti->vbus_state.mv, tcp_noti->vbus_state.ma);
            if (!tcp_noti->vbus_state.mv) {
                if (!chr_data->chr_info->ops->set_charge_mode) {
                    dev_err(chr_data->dev, "ops:set_charge_mode is null\n");
                    goto err;
                }
                chr_data->chr_info->ops->set_charge_mode(chr_data, CHARGE_MODE);
            } else {
                if (!chr_data->chr_info->ops->set_charge_mode) {
                    dev_err(chr_data->dev, "ops:set_charge_mode is null\n");
                    goto err;
                }
                chr_data->chr_info->ops->set_charge_mode(chr_data, OTG_MODE);
                if (!chr_data->chr_info->ops->set_otg_boost_voltage) {
                    dev_err(chr_data->dev, "ops:set_otg_boost_voltage is null\n");
                    goto err;
                }                
                chr_data->chr_info->ops->set_otg_boost_voltage(chr_data, 1); //设置成1v
                if (!chr_data->chr_info->ops->set_otg_boost_current) {
                    dev_err(chr_data->dev, "ops:set_otg_boost_current is null\n");
                    goto err;
                } 
                chr_data->chr_info->ops->set_otg_boost_current(chr_data, (tcp_noti->vbus_state.ma > 500) ? 1 : 0);
            }
            break;
        case TCP_NOTIFY_SINK_VBUS:
            dev_info(chr_data->dev, "source vbus mv:%d ma:%d\n",
                    tcp_noti->vbus_state.mv, tcp_noti->vbus_state.ma);
            if (!chr_data->chr_info->ops->set_charge_mode) {
                dev_err(chr_data->dev, "ops:set_charge_mode is null\n");
                goto err;
            }
            chr_data->chr_info->ops->set_charge_mode(chr_data, CHARGE_MODE);
            break;
        default:
            dev_err(chr_data->dev, "not support %d action\n", action);
    }
    mutex_unlock(&chr_data->mutex);
    dev_info(chr_data->dev, "%s end\n", __func__);
    return 0;
err:
    mutex_unlock(&chr_data->mutex);
    dev_info(chr_data->dev, "%s end\n", __func__);
    return -EINVAL;  
}
#endif

/*
* Charger register read/write/update bits
* @chr_data: Charger data
* @reg: Register address
* @value: Register value
*/
static int etek_charger_read_reg(struct chr_data *chr_data, unsigned int reg, unsigned int *value)
{
    return regmap_read(chr_data->regmap, reg, value);
}

/*
* Charger register write
* @chr_data: Charger data
* @reg: Register address
* @value: Register value
*/
static int etek_charger_write_reg(struct chr_data *chr_data, unsigned int reg, unsigned int value)
{
    return regmap_write(chr_data->regmap, reg, value);
}

/*
* Charger register update bits
* @chr_data: Charger data
* @reg: Register address
* @mask: Register mask
* @value: Register value
*/
static int etek_charger_update_bits(struct chr_data *chr_data, unsigned int reg, unsigned int mask, unsigned int value)
{
    return regmap_update_bits(chr_data->regmap, reg, mask, value);
}

/*
* Charger input type detect
* @chr_data: Charger data
*/
static void etek_charger_onetime_dpdm_detect(struct chr_data *chr_data)
{
    bool vbus_attached = false;
    unsigned int value = 0;

    if (etek_charger_read_reg(chr_data, CHR_REG0A, &value)) {
        dev_err(chr_data->dev, "Failed to obtain vbus connection status\n");
        return;
    }
    
    vbus_attached = (value & CHR_VBUS_GD) ? true : false;

    if (vbus_attached)
        etek_charger_update_bits(chr_data, CHR_REG07, CHR_IINDET_EN, CHR_IINDET_EN);
    else {
        dev_err(chr_data->dev, "There is no input connection and DPDM "
                                        "detection cannot be performed\n");
    }
}

static int etek_et95601ch_set_input_current(struct chr_data *chr_data, unsigned int cur)
{
    if (cur > 2400)
        cur = 2400;

    return etek_charger_update_bits(chr_data, CHR_REG00, 0x1F, cur / 100);
}

static int etek_et95601ch_set_input_voltage(struct chr_data *chr_data, unsigned int vol)
{
    if (vol > 5400)
        vol = 5400;

    return etek_charger_update_bits(chr_data, CHR_REG06, 0x0F, (vol - 3900) / 100);
}

static int etek_et95601ch_set_iprecharge_current(struct chr_data *chr_data, unsigned int cur)
{
    if (cur > 676)
        cur = 676;

    return etek_charger_update_bits(chr_data, CHR_REG03, 0xF0, cur / 52);
}

static int etek_et95601ch_set_itermcharge_current(struct chr_data *chr_data, unsigned int cur)
{
    if (cur > 780)
        cur = 780;

    return etek_charger_update_bits(chr_data, CHR_REG03, 0x0F, cur / 60);
}

static int etek_et95601ch_set_fastcharge_current(struct chr_data *chr_data, unsigned int cur)
{
    /* mA to uA */
    cur *= 1000;

    if (cur > 3047500)
        cur = 3047500;

    if (cur <= 1170000)
        return etek_charger_update_bits(chr_data, CHR_REG02, 0x3F, cur / 90000);
    else
        return etek_charger_update_bits(chr_data, CHR_REG02, 0x3F, ((cur - 805000) / 57500) + 0x0E);
}

static int etek_et95601ch_set_charge_voltage(struct chr_data *chr_data, unsigned int vol)
{
    if (vol > 4624)
        vol = 4624;

    return etek_charger_update_bits(chr_data, CHR_REG0E, 0x3F << 2, (vol / 16) << 2);
}

/*
* Set otg boost current
* @chr_data: Charger data
* @cur: Otg boost current 0:500mA 1:1200mA
*/
static int etek_et95601ch_set_otg_boost_current(struct chr_data *chr_data, unsigned int cur)
{
    if ((cur != 0) && (cur != 1)) {
        dev_err(chr_data->dev, "Invalid otg boost current,please set 0(500mA) or 1(1200mA)\n");
        return -EINVAL;
    }

    return etek_charger_update_bits(chr_data, CHR_REG02, 0x1 << 7, cur << 7);
}

/*
* Set otg boost voltage
* @chr_data: Charger data
* @vol: Otg boost voltage   00:4870mV 01:4998mV 10:5126mV 11:5254mV
*/
static int etek_et95601ch_set_otg_boost_voltage(struct chr_data *chr_data, unsigned int vol)
{
    if ((vol != 0) && (vol != 1) && (vol != 2) && (vol != 3)) {
        dev_err(chr_data->dev, "Invalid otg boost voltage,please set 0(4870mV) or 1(4998mV) or 2(5126mV) or 3(5254mV)\n");
        return -EINVAL;
    }

    return etek_charger_update_bits(chr_data, CHR_REG06, 0x3 << 4, vol << 4);
}

static int etek_et95601ch_set_charge_mode(struct chr_data *chr_data, enum chr_mode mode)
{
    switch (mode) {
        case OTG_MODE:
            etek_charger_update_bits(chr_data, CHR_REG01, 0x3 << 4, 0x2 << 4);
            break;
        case CHARGE_MODE:
            etek_charger_update_bits(chr_data, CHR_REG01, 0x3 << 4, 0x1 << 4);
            break;
        default:
            dev_err(chr_data->dev, "error charge mode\n");
            return -EINVAL;
    }

    return 0;
}

static int etek_et95601ch_get_input_current(struct chr_data *chr_data)
{
    unsigned int value = 0;

    if (etek_charger_read_reg(chr_data, CHR_REG00, &value))
        return -EINVAL;

    value = CHR_GENMASK(value, 4, 0, 0) * 100;
    dev_info(chr_data->dev, "input current: %dmA\n", value);

    return value;
}

static int etek_et95601ch_get_input_voltage(struct chr_data *chr_data)
{
    unsigned int value = 0;

    if (etek_charger_read_reg(chr_data, CHR_REG06, &value))
        return -EINVAL;

    value = CHR_GENMASK(value, 3, 0, 0) * 100 + 3900;
    dev_info(chr_data->dev, "input voltage: %dmV\n", value);

    return value;
}

static int etek_et95601ch_get_iprecharge_current(struct chr_data *chr_data)
{
    unsigned int value = 0;

    if (etek_charger_read_reg(chr_data, CHR_REG03, &value))
        return -EINVAL;

    value = CHR_GENMASK(value, 7, 4, 4) * 52;
    dev_info(chr_data->dev, "iprecharge current: %dmA\n", value);

    return value;
}

static int etek_et95601ch_get_itermcharge_current(struct chr_data *chr_data)
{
    unsigned int value = 0;

    if (etek_charger_read_reg(chr_data, CHR_REG03, &value))
        return -EINVAL;

    value = CHR_GENMASK(value, 4, 0, 0) * 60;
    dev_info(chr_data->dev, "itermcharge current: %dmA\n", value);

    return value;
}

static int etek_et95601ch_get_fastcharge_current(struct chr_data *chr_data)
{
    unsigned int value = 0;

    if (etek_charger_read_reg(chr_data, CHR_REG02, &value))
        return -EINVAL;

    value = CHR_GENMASK(value, 5, 0, 0);
    if (value <= 0x0D)
        value = value * 90000;
    else if (value > 0x0D)
        value = ((value - 0x0D) * 57500) + 805000;

    dev_info(chr_data->dev, "fastcharge current: %dmA\n", value);

    return value;
}

static int etek_et95601ch_get_charge_voltage(struct chr_data *chr_data)
{
    unsigned int value = 0;

    if (etek_charger_read_reg(chr_data, CHR_REG0E, &value))
        return -EINVAL;

    value = (CHR_GENMASK(value, 7, 2, 2) * 16) + 3856;
    dev_info(chr_data->dev, "charge voltage: %dmV\n", value);

    return value;
}

static int etek_et95601ch_get_otg_boost_current(struct chr_data *chr_data)
{
    unsigned int value = 0;

    if (etek_charger_read_reg(chr_data, CHR_REG02, &value))
        return -EINVAL;

    value = CHR_GENMASK(value, 7, 7, 7);
    if (value == 0x1)
        value = 1200;
    else if (value == 0x0)
        value = 500;        

    dev_info(chr_data->dev, "otg boost current: %dmA\n", value);

    return value;
}

static int etek_et95601ch_get_otg_boost_voltage(struct chr_data *chr_data)
{
    unsigned int value = 0;

    if (etek_charger_read_reg(chr_data, CHR_REG06, &value))
        return -EINVAL;

    value = CHR_GENMASK(value, 5, 4, 4);
    switch (value) {
        case 0x0:
            value = 4870;
            break;
        case 0x1:
            value = 4998;
            break;
        case 0x2:
            value = 5126;
            break;
        case 0x3:
            value = 5254;
            break;
    }
    dev_info(chr_data->dev, "otg boost voltage: %dmV\n", value);

    return value;
}   

static enum chr_mode etek_et95601ch_get_charge_mode(struct chr_data *chr_data)
{
    unsigned int value = 0;

    etek_charger_read_reg(chr_data, CHR_REG01, &value);
    value = CHR_GENMASK(value, 5, 4, 4);

    if (value == 0x2)
        return OTG_MODE;
    else if (value == 0x1)
        return CHARGE_MODE;
    else
        return ERROR_MODE;
    
}

static int etek_et95601ch_get_charge_type(struct chr_data *chr_data)
{
    unsigned int value = 0;

    if (etek_charger_read_reg(chr_data, CHR_REG08, &value))
        return -EINVAL;

    value = CHR_GENMASK(value, 7, 5, 5);
    dev_info(chr_data->dev, "charge type: %d\n", value);
    switch (value) {
        case 0x0:
        case 0x5:
        case 0x6:
            return POWER_SUPPLY_TYPE_UNKNOWN;
        case 0x1:
            return POWER_SUPPLY_TYPE_USB;
        case 0x2:
            return POWER_SUPPLY_TYPE_USB_CDP;
        case 0x3:
            return POWER_SUPPLY_TYPE_USB_DCP;
        case 0x7:
            return POWER_SUPPLY_TYPE_USB_TYPE_C;
        default:
            return POWER_SUPPLY_TYPE_UNKNOWN; 
    }
}

static int etek_et95601ch_get_charge_status(struct chr_data *chr_data)
{
    unsigned int value = 0;

    if (etek_charger_read_reg(chr_data, CHR_REG08, &value))
        return -EINVAL;

    value = CHR_GENMASK(value, 4, 3, 3);
    dev_info(chr_data->dev, "charge status: %d\n", value);
    switch (value) {
        case 0x0:
            return POWER_SUPPLY_STATUS_DISCHARGING;
        case 0x3:
            return POWER_SUPPLY_STATUS_FULL;
        case 0x1:
        case 0x2:
            return POWER_SUPPLY_STATUS_CHARGING;
        default:
            return POWER_SUPPLY_STATUS_UNKNOWN;
    }
}

static const enum power_supply_usb_type etek_et95601ch_usb_type[] = {
    POWER_SUPPLY_USB_TYPE_UNKNOWN,
	POWER_SUPPLY_USB_TYPE_SDP,
	POWER_SUPPLY_USB_TYPE_CDP,
	POWER_SUPPLY_USB_TYPE_DCP,
	POWER_SUPPLY_USB_TYPE_ACA,
};

static const enum power_supply_property etek_et95601ch_power_supply_props[] = {
    POWER_SUPPLY_PROP_STATUS,   //State of charge
    POWER_SUPPLY_PROP_CHARGE_TYPE, //Type of charge
	POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, //Input current limit
	POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, //Input voltage limit
	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, //Constant charge current
	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, //Constant charge voltage
	POWER_SUPPLY_PROP_PRECHARGE_CURRENT, //Precharge current
	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, //Charge term current
};

static int etek_et95601ch_set_power_supply_property(struct power_supply *psy,
	enum power_supply_property psp, const union power_supply_propval *val)
{
    struct chr_data *chr_data = (struct chr_data *)psy->drv_data;

    /*  */
    mutex_lock(&chr_data->mutex);
    switch (psp) {
        case POWER_SUPPLY_PROP_STATUS:
            dev_info(chr_data->dev, "not support set status\n");
            goto no_support;
        case POWER_SUPPLY_PROP_CHARGE_TYPE:
            if (!chr_data->chr_info->ops->set_charge_mode) {
                dev_err(chr_data->dev, "not support set charge type\n");
                goto no_support;
            }
            switch (val->intval) {
                case POWER_SUPPLY_CHARGE_TYPE_NONE:
                    chr_data->chr_info->ops->set_charge_mode(chr_data, OTG_MODE);
                    break;
                case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
                case POWER_SUPPLY_CHARGE_TYPE_FAST:
                    chr_data->chr_info->ops->set_charge_mode(chr_data, CHARGE_MODE);
                    break;
                default:
                    dev_err(chr_data->dev, "invalid charge type\n");
                    goto no_support;
            }
            break;
        case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
            if (!chr_data->chr_info->ops->set_input_current) {
                dev_err(chr_data->dev, "not support set input current\n");
                goto no_support;
            }
            chr_data->chr_info->ops->set_input_current(chr_data, val->intval);
            break;
        case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
            if (!chr_data->chr_info->ops->set_input_voltage) {
                dev_err(chr_data->dev, "not support set input voltage\n");
                goto no_support;
            }
            chr_data->chr_info->ops->set_input_voltage(chr_data, val->intval);
            break;
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
            if (!chr_data->chr_info->ops->set_fastcharge_current) {
                dev_err(chr_data->dev, "not support set fastcharge current\n");
                goto no_support;
            }
            chr_data->chr_info->ops->set_fastcharge_current(chr_data, val->intval);
            break;
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
            if (!chr_data->chr_info->ops->set_charge_voltage) {
                dev_err(chr_data->dev, "not support set charge voltage\n");
                goto no_support;
            }
            chr_data->chr_info->ops->set_charge_voltage(chr_data, val->intval);
            break;
        case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
            if (!chr_data->chr_info->ops->set_iprecharge_current) {
                dev_err(chr_data->dev, "not support set precharge current\n");
                goto no_support;
            }
            chr_data->chr_info->ops->set_iprecharge_current(chr_data, val->intval);
            break;
        case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
            if (!chr_data->chr_info->ops->set_itermcharge_current) {
                dev_err(chr_data->dev, "not support set charge term current\n");
                goto no_support;
            }
            chr_data->chr_info->ops->set_itermcharge_current(chr_data, val->intval);
            break;
        default:
            dev_err(chr_data->dev, "invalid property\n");
            goto no_support;
    }
no_support:

    mutex_unlock(&chr_data->mutex);
    return 0;
}

static int etek_et95601ch_get_power_supply_property(struct power_supply *psy,
	enum power_supply_property psp, union power_supply_propval *val)
{
    struct chr_data *chr_data = (struct chr_data *)psy->drv_data;

    mutex_lock(&chr_data->mutex);
    switch (psp) {
        case POWER_SUPPLY_PROP_STATUS:
            if (!chr_data->chr_info->ops->get_charge_status) {
                dev_err(chr_data->dev, "not support get charge type\n");
                goto no_support;
            }
            val->intval = chr_data->chr_info->ops->get_charge_status(chr_data);
            break;
        case POWER_SUPPLY_PROP_CHARGE_TYPE:
            if (!chr_data->chr_info->ops->get_charge_type) {
                dev_err(chr_data->dev, "not support get charge type\n");
                goto no_support;
            }
            val->intval = chr_data->chr_info->ops->get_charge_type(chr_data);
            break;
        case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
            if (!chr_data->chr_info->ops->get_input_current) {
                dev_err(chr_data->dev, "not support get input current\n");
                goto no_support;
            }
            val->intval = chr_data->chr_info->ops->get_input_current(chr_data);
            break;
        case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
            if (!chr_data->chr_info->ops->get_input_voltage) {
                dev_err(chr_data->dev, "not support get input voltage\n");
                goto no_support;
            }
            val->intval = chr_data->chr_info->ops->get_input_voltage(chr_data);
            break;
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
            if (!chr_data->chr_info->ops->get_fastcharge_current) {
                dev_err(chr_data->dev, "not support get fastcharge current\n");
                goto no_support;
            }
            val->intval = chr_data->chr_info->ops->get_fastcharge_current(chr_data);
            break;
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
            if (!chr_data->chr_info->ops->get_charge_voltage) {
                dev_err(chr_data->dev, "not support get charge voltage\n");
                goto no_support;
            }
            val->intval = chr_data->chr_info->ops->get_charge_voltage(chr_data);
            break;
        case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
            if (!chr_data->chr_info->ops->get_iprecharge_current) {
                dev_err(chr_data->dev, "not support get precharge current\n");
                goto no_support;
            }
            val->intval = chr_data->chr_info->ops->get_iprecharge_current(chr_data);
            break;
        case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
            if (!chr_data->chr_info->ops->get_itermcharge_current) {
                dev_err(chr_data->dev, "not support get charge term current\n");
                goto no_support;
            }
            val->intval = chr_data->chr_info->ops->get_itermcharge_current(chr_data);
            break;
        default:
            dev_err(chr_data->dev, "invalid property\n");
            goto no_support;
    }

no_support:
    mutex_unlock(&chr_data->mutex);

    return 0;
}

static struct chr_ops etek_chr_ops_table[] = {
    [CHR_DEVICE_ID_ET95601CH] = {
        .set_input_current = etek_et95601ch_set_input_current,
        .set_input_voltage = etek_et95601ch_set_input_voltage,
        .set_iprecharge_current = etek_et95601ch_set_iprecharge_current,
        .set_itermcharge_current = etek_et95601ch_set_itermcharge_current,
        .set_fastcharge_current = etek_et95601ch_set_fastcharge_current,
        .set_charge_voltage = etek_et95601ch_set_charge_voltage,
        .set_otg_boost_current = etek_et95601ch_set_otg_boost_current,
        .set_otg_boost_voltage = etek_et95601ch_set_otg_boost_voltage,
        .set_charge_mode = etek_et95601ch_set_charge_mode,
        .get_input_current = etek_et95601ch_get_input_current,
        .get_input_voltage = etek_et95601ch_get_input_voltage,
        .get_iprecharge_current = etek_et95601ch_get_iprecharge_current,
        .get_itermcharge_current = etek_et95601ch_get_itermcharge_current,
        .get_fastcharge_current = etek_et95601ch_get_fastcharge_current,
        .get_charge_voltage = etek_et95601ch_get_charge_voltage,
        .get_otg_boost_current = etek_et95601ch_get_otg_boost_current,
        .get_otg_boost_voltage = etek_et95601ch_get_otg_boost_voltage,
        .get_charge_mode = etek_et95601ch_get_charge_mode,
        .get_charge_type = etek_et95601ch_get_charge_type,
        .get_charge_status = etek_et95601ch_get_charge_status,
    },
};

/*
* Charger register map config
* @reg_bits: Register address bits
* @val_bits: Register value bits
*/
static struct regmap_config etek_charger_regmap_config[] = {
    [CHR_DEVICE_ID_ET95601CH] = {
        .reg_bits = 8,
        .val_bits = 8,
        .max_register = CHR_REG11,
    },
};

static struct power_supply_desc etek_chr_supply_table[] = {
    [CHR_DEVICE_ID_ET95601CH] = {
        .name = "et95601ch",
        .type = POWER_SUPPLY_TYPE_USB,
        .usb_types = etek_et95601ch_usb_type,
        .num_usb_types = ARRAY_SIZE(etek_et95601ch_usb_type),
        .properties = etek_et95601ch_power_supply_props,
        .num_properties = ARRAY_SIZE(etek_et95601ch_power_supply_props),
        .set_property = etek_et95601ch_set_power_supply_property,
        .get_property = etek_et95601ch_get_power_supply_property,
    },
};

/*
* Charger information table
* @name: Charger name
* @id: Charger id
* @regmap_config: Charger register map config
* @ops: Charger operations
*/
static struct etek_chr_info etek_chr_info_table[] = {
    [CHR_DEVICE_ID_ET95601CH] = {
        .name = "et95601ch",
        .id = CHR_DEVICE_ID_ET95601CH,
        .regmap_config = &etek_charger_regmap_config[CHR_DEVICE_ID_ET95601CH],
        .ops = &etek_chr_ops_table[CHR_DEVICE_ID_ET95601CH],
        .psy_desc = &etek_chr_supply_table[CHR_DEVICE_ID_ET95601CH],
    },
};

/*
* Charger work handler
* @work: Charger work
*/
static void etek_charger_work_handler(struct work_struct *work)
{
    struct chr_data *chr_data = container_of(work, struct chr_data, work.work);
    unsigned int vbus_state;

    mutex_lock(&chr_data->mutex);
    vbus_state = chr_data->state.curr_vbus_state;
    dev_info(chr_data->dev, "vbus state: %d\n", vbus_state);
    if (vbus_state == 0x0) {
        dev_info(chr_data->dev, "The vbus of typec is disconnected\n");
    } else {
        dev_info(chr_data->dev, "The vbus of typec is connected\n");
    }

    if (vbus_state == 0x7) {
        chr_data->chr_info->ops->set_charge_mode(chr_data, OTG_MODE);
    } else {
        chr_data->chr_info->ops->set_charge_mode(chr_data, CHARGE_MODE);
    }
    mutex_unlock(&chr_data->mutex);
}

/*
* Charger irq handler
* @irq: Charger irq number
* @dev_id: Charger device id
*/
static irqreturn_t etek_charger_irq_handler(int irq, void *dev_id)
{
    struct chr_data *chr_data = (struct chr_data *)dev_id;
    unsigned int alert_reg08 = 0;

    etek_charger_read_reg(chr_data, CHR_REG08, &alert_reg08);
    mutex_lock(&chr_data->mutex);
    /* Get the vbus state from the alert register */
    chr_data->state.curr_vbus_state = CHR_GENMASK(alert_reg08, 7, 5, 5);
    if (chr_data->state.prev_vbus_state != chr_data->state.curr_vbus_state) {
        dev_info(chr_data->dev, "vbus state changed prev: %d, curr: %d\n",
            chr_data->state.prev_vbus_state, chr_data->state.curr_vbus_state);
        chr_data->state.prev_vbus_state = chr_data->state.curr_vbus_state;
        /* Schedule the work to be executed after 10ms */
        schedule_delayed_work(&chr_data->work, msecs_to_jiffies(10));
    }
    mutex_unlock(&chr_data->mutex);

    return IRQ_HANDLED;
}

/*
* Charger hardware initialization
* @chr_data: Charger data
*/
static int etek_charger_hw_init(struct chr_data *chr_data)
{
    //set input current
    if (chr_data->chr_info->ops->set_input_current(chr_data, chr_data->charger_data.input_i)) {
        dev_err(chr_data->dev, "Failed to set input current\n");
        return -EINVAL;
    }
    //set input voltage
    if (chr_data->chr_info->ops->set_input_voltage(chr_data, chr_data->charger_data.input_v)) {
        dev_err(chr_data->dev, "Failed to set input voltage\n");
        return -EINVAL;
    }

    //set iprecharge current
    if (chr_data->chr_info->ops->set_iprecharge_current(chr_data, chr_data->charger_data.iprechg_i)) {
        dev_err(chr_data->dev, "Failed to set iprecharge current\n");
        return -EINVAL;
    } 
    //set itermcharge current
    if (chr_data->chr_info->ops->set_itermcharge_current(chr_data, chr_data->charger_data.iterm_i)) {
        dev_err(chr_data->dev, "Failed to set itermcharge current\n");
        return -EINVAL;
    }   
    //set fastcharge current
    if (chr_data->chr_info->ops->set_fastcharge_current(chr_data, chr_data->charger_data.fast_charge_i)) {
        dev_err(chr_data->dev, "Failed to set fastcharge current\n");
        return -EINVAL;
    }
    //set fastcharge voltage
    if (chr_data->chr_info->ops->set_charge_voltage(chr_data, chr_data->charger_data.fast_charge_v)) {
        dev_err(chr_data->dev, "Failed to set fastcharge voltage\n");
        return -EINVAL;
    }
    //set boost voltage
    if (chr_data->chr_info->ops->set_otg_boost_voltage(chr_data, chr_data->charger_data.boost_v)) {
        dev_err(chr_data->dev, "Failed to set boost voltage\n");
        return -EINVAL;
    }
    //set boost current
    if (chr_data->chr_info->ops->set_otg_boost_current(chr_data, chr_data->charger_data.boost_i)) {
        dev_err(chr_data->dev, "Failed to set boost current\n");
        return -EINVAL;
    }
    //set charge mode
    if (chr_data->chr_info->ops->set_charge_mode(chr_data, CHARGE_MODE)) {
        dev_err(chr_data->dev, "Failed to set charge mode\n");
        return -EINVAL;
    }

    return 0;
}

/*
* Charger get device tree data
* @dev: Charger device
* @chr_data: Charger data
*/
static int etek_charger_get_device_tree_data(struct device *dev, struct chr_data *chr_data)
{
    struct device_node *np;
    unsigned int ret;

    np = dev->of_node;

    ret = of_get_named_gpio(np, "gpio_intr", 0);
    if (ret < 0) {
        dev_err(dev, "Failed to get gpio_intr from device tree\n");
        return ret;
    } else {
        chr_data->gpio_num = ret;
        dev_info(dev, "gpio_intr: %d\n", chr_data->gpio_num);
        chr_data->irq_num = gpio_to_irq(chr_data->gpio_num);
        dev_info(dev, "irq_num: %d\n", chr_data->irq_num);
    }

    ret = of_property_read_u32(np, "input_v", &chr_data->charger_data.input_v);
    if (ret < 0) {
        dev_err(dev, "Failed to get input_v from device tree\n");
        return ret;
    }
    dev_info(dev, "input_v: %d\n", chr_data->charger_data.input_v);

    ret = of_property_read_u32(np, "input_i", &chr_data->charger_data.input_i);
    if (ret < 0) {
        dev_err(dev, "Failed to get input_i from device tree\n");
        return ret;
    }
    dev_info(dev, "input_i: %d\n", chr_data->charger_data.input_i);

    ret = of_property_read_u32(np, "iprechg_i", &chr_data->charger_data.iprechg_i);
    if (ret < 0) {
        dev_err(dev, "Failed to get iprechg_i from device tree\n");
        return ret;
    }
    dev_info(dev, "iprechg_i: %d\n", chr_data->charger_data.iprechg_i);

    ret = of_property_read_u32(np, "iterm_i", &chr_data->charger_data.iterm_i);
    if (ret < 0) {
        dev_err(dev, "Failed to get iterm_i from device tree\n");
        return ret;
    }
    dev_info(dev, "iterm_i: %d\n", chr_data->charger_data.iterm_i);

    ret = of_property_read_u32(np, "fast_charge_i", &chr_data->charger_data.fast_charge_i);
    if (ret < 0) {
        dev_err(dev, "Failed to get fast_charge_i from device tree\n");
        return ret;
    }
    dev_info(dev, "fast_charge_i: %d\n", chr_data->charger_data.fast_charge_i);

    ret = of_property_read_u32(np, "fast_charge_v", &chr_data->charger_data.fast_charge_v);
    if (ret < 0) {
        dev_err(dev, "Failed to get fast_charge_v from device tree\n");
        return ret;
    }
    dev_info(dev, "fast_charge_v: %d\n", chr_data->charger_data.fast_charge_v);

    ret = of_property_read_u32(np, "boost_v", &chr_data->charger_data.boost_v);
    if (ret < 0) {
        dev_err(dev, "Failed to get boost_v from device tree\n");
        return ret;
    }
    dev_info(dev, "boost_v: %d\n", chr_data->charger_data.boost_v);

    ret = of_property_read_u32(np, "boost_i", &chr_data->charger_data.boost_i);
    if (ret < 0) {
        dev_err(dev, "Failed to get boost_i from device tree\n");
        return ret;
    }
    dev_info(dev, "boost_i: %d\n", chr_data->charger_data.boost_i);

    return 0;
}

static const struct of_device_id etek_charger_of_match[] = {
    { .compatible = "etek,et95601ch", },
    { }
};
MODULE_DEVICE_TABLE(of, etek_charger_of_match);

static const struct i2c_device_id etek_charger_id[] = {
    { "et95601ch", CHR_DEVICE_ID_ET95601CH },
    { }
};
MODULE_DEVICE_TABLE(i2c, etek_charger_id);

static int etek_charger_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct chr_data *chr_data;
    int ret;
    struct power_supply_config psy_cfg = {0};

    chr_data = (struct chr_data *)devm_kzalloc(&client->dev, sizeof(struct chr_data), GFP_KERNEL);
    if (!chr_data) {
        dev_err(&client->dev, "Failed to allocate memory for charger data\n");
        return -ENOMEM;
    }

    /* Initialize the variables in the chr_data structure */
    chr_data->client = client;
    chr_data->dev = &client->dev;
    chr_data->chr_info = &etek_chr_info_table[id->driver_data];
    psy_cfg.drv_data = chr_data;
    psy_cfg.of_node = client->dev.of_node;
    /* prev_vbus_state and curr_vbus_state are initialized to 0(NO_INPUT) */
    chr_data->state.prev_vbus_state = 0;
    chr_data->state.curr_vbus_state = 0;
    mutex_init(&chr_data->mutex);
    INIT_DELAYED_WORK(&chr_data->work, etek_charger_work_handler);
    dev_set_drvdata(chr_data->dev, chr_data);

    /* Initialize the regmap */
    chr_data->regmap = devm_regmap_init_i2c(client, &chr_data->chr_info->regmap_config[CHR_DEVICE_ID_ET95601CH]);
    if (IS_ERR(chr_data->regmap)) {
        dev_err(chr_data->dev, "Failed to initialize regmap\n");
        return PTR_ERR(chr_data->regmap);
    }

    /* Get device tree data */
    ret = etek_charger_get_device_tree_data(chr_data->dev, chr_data);
    if (ret < 0) {
        dev_err(chr_data->dev, "Failed to get device tree data\n");
        return ret;
    }

    /* Hardware initialization */
    ret = etek_charger_hw_init(chr_data);
    if (ret < 0) {
        dev_err(chr_data->dev, "Failed to initialize hardware\n");
        return ret;
    }

#if PD_AND_CHARGER_NOTIFIY
    chr_data->tcpc = charger_dev_get_by_name("type_c_port0");
    if (!(chr_data->tcpc)) {
        dev_err(chr_data->dev, "charger get tcpc struct fail\n");
        return -EINVAL;
    }
    dev_info(chr_data->dev, "tcpc name = %s\n", chr_data->tcpc->desc.name);
    chr_data->nb.notifier_call = charger_srcu_notifier_func;
    if (srcu_notifier_chain_register(&chr_data->tcpc->evt_nh[TCP_NOTIFY_IDX_VBUS], &chr_data->nb)) {
        dev_err(chr_data->dev, "srcu notifiy register fail\n");
        return -EINVAL;
    }
#endif

    chr_data->psy = devm_power_supply_register(chr_data->dev, &chr_data->chr_info->psy_desc[CHR_DEVICE_ID_ET95601CH], &psy_cfg);
    if (IS_ERR(chr_data->psy)) {
        dev_err(chr_data->dev, "Failed to register power supply\n");
        return PTR_ERR(chr_data->psy);
    }
    /* Request irq */
    ret = devm_request_threaded_irq(chr_data->dev, chr_data->irq_num, NULL, etek_charger_irq_handler,
                            IRQF_TRIGGER_LOW | IRQF_ONESHOT, chr_data->chr_info->name, chr_data);
    if (ret < 0) {
        dev_err(chr_data->dev, "Failed to request irq\n");
        return ret;
    }

    /* Perform DPDM detection */
    etek_charger_onetime_dpdm_detect(chr_data);

    dev_info(chr_data->dev, "etek charger(%s) probe success\n", chr_data->chr_info->name);
    return 0;
}

static int etek_charger_remove(struct i2c_client *client)
{
    struct chr_data *chr_data = dev_get_drvdata(&client->dev);

    cancel_delayed_work_sync(&chr_data->work);

    pr_info("The charging ic with address %d has been removed\n", client->addr);
    return 0;
}

static struct i2c_driver etek_charger_driver = {
    .driver = {
        .name = "etek charger",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(etek_charger_of_match),
    },
    .probe = etek_charger_probe,
    .remove = etek_charger_remove,
    .id_table = etek_charger_id,
};

//init charger IC driver
static int __init etek_charger_init(void) 
{
    int ret;

    ret = i2c_add_driver(&etek_charger_driver);
    if (ret) {
        pr_err("etek charger ic failed to register i2c driver\n");
        return ret;
    }

    pr_info("etek charger driver init success\n");
    return 0;
}

//exit charger IC driver
static void __exit etek_charger_exit(void)
{
    i2c_del_driver(&etek_charger_driver);

    pr_info("etek charger driver exit success\n");
}

module_init(etek_charger_init);
module_exit(etek_charger_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("hanhao@etek.com.cn");
MODULE_DESCRIPTION("ETEK charging IC driver code");
